package com.googlecode.imanager.jasper;

import java.awt.Color;
import java.util.Collections;
import java.util.Iterator;
import java.util.List;

import net.sf.jasperreports.engine.JRPrintPage;
import net.sf.jasperreports.engine.JasperPrint;
import net.sf.jasperreports.engine.base.JRBasePrintPage;
import net.sf.jasperreports.engine.base.JRBasePrintRectangle;

import com.googlecode.imanager.client.Container;
import com.googlecode.imanager.client.Document;
import com.googlecode.imanager.client.Manager;
import com.googlecode.imanager.client.event.EvaluationPhase;
import com.googlecode.imanager.client.style.Orientation;
import com.googlecode.imanager.client.style.PageSettings;
import com.googlecode.imanager.jasper.print.RChild;
import com.googlecode.imanager.jasper.print.RFrame;
import com.googlecode.imanager.jasper.util.RHorizontalPrintComparator;
import com.googlecode.imanager.jasper.util.RPrintComparator;
import com.googlecode.imanager.jasper.util.RVerticalPrintComparator;

/**
 * @author wellsantos@wellsantos.com
 * @since 0.01
 */
public class PageBuilder {
    
    protected final RDocument report;
    protected final Document document;
    protected final JasperPrint print;
    protected PageSettings pageSettings;

    private JRBasePrintPage currentPage;
    private boolean singlePage;
    private boolean vertical;
    
    private int pageIndex;
    private int pageCount;
    private int pageLength;
    private int pageOffset;
    
    private int left;
    private int top;
    private int right;
    private int bottom;
    private int width;
    private int height;
        
    private RPrintComparator comparator;

    
    public PageBuilder(RDocument report, JasperPrint print) {
        this.print = print;
        this.report = report;
        this.document = report.getSource();
    }

    public Document getDocument() {
        return document;
    }

    public JasperPrint getPrint() {
        return print;
    }

    protected void readSettings(boolean ignoreMargins) {
        if (pageSettings == null){
            // TODO pageSettings = document.getPageSettings();

            if (pageSettings != null){
                pageSettings = pageSettings.clone();
            } else {
                pageSettings = new PageSettings();
            }
        }
        
        vertical = pageSettings.getOrientation() != Orientation.HORIZONTAL;

        if (ignoreMargins){
            left = 0;
            top = 0;
            right = 0;
            bottom = 0;
        } else {
            left = pageSettings.getMarginLeft();
            top = pageSettings.getMarginTop();
            right = pageSettings.getMarginRight();
            bottom = pageSettings.getMarginBottom();
        }
        
        width = pageSettings.getPageWidth() - right - left;
        height = pageSettings.getPageHeight() - bottom - top;


        byte orientation = 1;
        if (!vertical){
            orientation = 2;
        }

        print.setName(document.getTitle());
        print.setPageWidth(pageSettings.getPageWidth());
        print.setPageHeight(pageSettings.getPageHeight());
        print.setOrientation(orientation);
        // TODO pageSettings.getAuthors() , comments, etc
        //this.fontsMap = new HashMap();
        //this.fontsList = new ArrayList();
        //this.stylesMap = new HashMap();
        //this.stylesList = new ArrayList();
        //this.originsMap = new HashMap();
        //this.originsList = new ArrayList();
        //this.pages = new ArrayList();
        //this.anchorIndexes = null;
        //this.defaultStyleProvider = new DefaultStyleProvider(null, null);
        //this.propertiesMap = new JRPropertiesMap();        
    }

    public void buildSinglePage(boolean ignoreMargins) {
        doBuild(true, ignoreMargins);
    }
    
    public void buildPageList(boolean ignoreMargins) {
        doBuild(false, ignoreMargins);
    }
    
    protected void doBuild(boolean singlePage, boolean ignoreMargins){
        //report = (RDocument) getManager.getImpl(document);

            this.singlePage = singlePage;
            pageIndex = 0;
            pageCount = 0;
            
            readSettings(ignoreMargins);
            
            if (width <= 0
                    || height <= 0){
                report.setPageCount(pageCount);
                report.setPageIndex(pageIndex);
                return;
            }
            
            if (vertical){
                pageLength = height;
                comparator = RVerticalPrintComparator.INSTANCE;
            } else {
                pageLength = width;
                comparator = RHorizontalPrintComparator.INSTANCE;
            }
            
            pageOffset = 0;
            currentPage = new JRBasePrintPage();

            buildDocument();
            
            // TODO remover os Controls que tem visible==false
            
            pageCount++;
            report.setPageCount(pageCount);
            print.addPage(currentPage);

            if (singlePage){
                width = report.getTarget().getWidth() + report.getTarget().getRInsets().getRight();
                height = report.getTarget().getHeight() + report.getTarget().getRInsets().getBottom();
                
                print.setPageWidth(width + right + left);
                print.setPageHeight(height + bottom + top);                
            }
            
            validatePages();
            decoratePages();
            dispose();
    }
    
    @SuppressWarnings("unchecked")
    protected void decoratePages() {
        if ((left | top | right | bottom) == 0) {
            return;
        }
        
        final Color pageColor = Color.WHITE;

        JRBasePrintRectangle leftRectangle = null;
        JRBasePrintRectangle topRectangle = null;
        JRBasePrintRectangle rightRectangle = null;
        JRBasePrintRectangle bottomRectangle = null;

        if (left > 0){
            JRBasePrintRectangle rect = new JRBasePrintRectangle(print.getDefaultStyleProvider());
            rect.setX(0);
            rect.setY(0);
            rect.setWidth(left);
            rect.setHeight(top + height);
            rect.setBackcolor(pageColor);
            rect.setForecolor(pageColor);
            leftRectangle = rect;
        }

        if (top > 0){
            JRBasePrintRectangle rect = new JRBasePrintRectangle(print.getDefaultStyleProvider());
            rect.setX(left);
            rect.setY(0);
            rect.setWidth(width + right);
            rect.setHeight(top);
            rect.setBackcolor(pageColor);
            rect.setForecolor(pageColor);
            topRectangle = rect;
        }

        if (right > 0){
            JRBasePrintRectangle rect = new JRBasePrintRectangle(print.getDefaultStyleProvider());
            rect.setX(left + width);
            rect.setY(top);
            rect.setWidth(right);
            rect.setHeight(height + bottom);
            rect.setBackcolor(pageColor);
            rect.setForecolor(pageColor);
            rightRectangle = rect;
        }

        if (bottom > 0){
            JRBasePrintRectangle rect = new JRBasePrintRectangle(print.getDefaultStyleProvider());
            rect.setX(0);
            rect.setY(top + height);
            rect.setWidth(left + width);
            rect.setHeight(bottom);
            rect.setBackcolor(pageColor);
            rect.setForecolor(pageColor);
            bottomRectangle = rect;
        }

        for (JRBasePrintPage page : (List<JRBasePrintPage>) print.getPages()){
            if (leftRectangle != null){
                page.addElement(leftRectangle);
            }
            if (topRectangle != null){
                page.addElement(topRectangle);
            }
            if (rightRectangle != null){
                page.addElement(rightRectangle);
            }
            if (bottomRectangle != null){
                page.addElement(bottomRectangle);
            }
        }
    }

    protected void buildDocument(){
        // TODO esticar a pagina de acordo com a orientation
        
        RFrame frame = report.getTarget();

        frame.setX(frame.getX() + left);
        frame.setY(frame.getY() + top);
        currentPage.addElement(frame);
        
        buildControl(frame);
        buildContainer(frame, 0);
    }

    @SuppressWarnings("unchecked")
    protected int buildContainer(/*LinkedList<RContainer> bands,*/ RFrame frame, int parentOffset){
        Collections.sort(frame.getElements(), comparator);        
        int acumulator = 0;
        
        for (int i=0, size=frame.getElements().size(); i<size; i++){
            RChild child = (RChild) frame.getElements().get(i);
            buildControl(child);
                        
            // check for headers/footers
            
            RControl owner = child.getROwner();
            if (owner.getSource() instanceof Container){
                Container c = (Container) owner.getSource();
                
                if (c.getHeader() != null
                        || c.getFooter() != null) {
                   // bands.add((RContainer) owner);
                }
            }
            
            // reposition page elements

            int childPos = comparator.addStartPos(child, acumulator);
            int absOffset = childPos + parentOffset + pageOffset;
            
            if (absOffset > pageLength){
                RControl control = child.getROwner(); 
                breakPage(control/*, absOffset*/);
            }
            
            if (child instanceof RFrame){
                acumulator += buildContainer((RFrame) child, 
                        parentOffset + comparator.getStartPos(frame));
                
                frame = child.getROwner().getParentFrame();
                size = frame.getElements().size();
            }
        }
        
        RChild lastChild = null;

        for (int i=0, size=frame.getElements().size(); i<size; i++){
            RChild child = (RChild) frame.getElements().get(i);
            
            if (lastChild == null){
                lastChild = child;
            }
            else if (comparator.compareEndPos(child, lastChild) > 0) {
                lastChild = child;
            }
        }
        
        acumulator = 0;
        
        if (lastChild != null){
            int len = comparator.getLength(frame);
            int pos = comparator.getEndPos(lastChild);
            if (len < pos){
                acumulator = pos - len;
                comparator.setLength(frame, pos);
            }
        }
        
        return acumulator;
    }
        
    protected void buildControl(RChild child) {
        // nothing to do
    }

    @SuppressWarnings("unchecked")
    protected void breakPage(final RControl control){
        
        if (singlePage){
            return;
        }
        
        print.addPage(currentPage);
        currentPage = new JRBasePrintPage();
        
        pageIndex++;
        pageCount++;
        pageOffset -= pageLength;

        //LinkedList<RContainer> hierarchy = new LinkedList<RContainer>();
        Container source = control.getSource().getParent();
        RChild oldChild = control.getTarget();
        RChild newChild = oldChild;
        
        while (source != null) {
            RContainer container = (RContainer) Manager.getImpl(source);
            //hierarchy.add(container);
            
            //absLeft += container.target.getX() - container.getTarget().getRInsets().getLeft();
            //absTop += container.target.getY() - container.getTarget().getRInsets().getTop();
            
            // duplicate parent hierarchy
            
            RFrame oldParent = (RFrame) container.cloneTarget();
                        
            List<?> elements = oldParent.getElements();
            int moveIndex = elements.indexOf(oldChild);

            RFrame newParent = container.getTarget();
            newParent.getElements().addAll(elements);
            
            // exchange children of page
            
            newChild.getROwner().setParentFrame(newParent);
            newParent.getElements().set(moveIndex, newChild);
            moveIndex++;

            while (moveIndex < elements.size()){                    
                RChild next = (RChild) elements.get(moveIndex);
                elements.remove(moveIndex);
                next.getROwner().setParentFrame(newParent);
            }
            
            oldChild = oldParent;
            newChild = newParent;
            source = source.getParent();
        }
        
        comparator.addStartPos(newChild, -pageLength);
        currentPage.addElement(newChild);
        
        /*
        int absLeft = 0;//control.target.getX() - control.getTarget().getRInsets().getLeft();
        int absTop = 0;//control.target.getY() - control.getTarget().getRInsets().getTop();        
        
        while (!hierarchy.isEmpty()) {
            RContainer container = hierarchy.removeLast();
            RChild target = container.target;
            
            absLeft += target.getX() - target.getRInsets().getLeft();
            absTop += target.getY() - target.getRInsets().getTop();
            
            generateHeaderFooter(container, true, control, absLeft, absTop);
            generateHeaderFooter(container, false, control, absLeft, absTop);
            
            source = source.getParent();
        }*/
        
        report.setPageCount(pageCount);
        report.setPageIndex(pageIndex);
        
        report.getBridgeApplication().firePhaseEvent(document, EvaluationPhase.PAGE_BREAK);
    }
    
    /*private void generateHeaderFooter(RContainer container, boolean isHeader, 
                            RControl broken, int absSourceLeft, int absSourceTop,
                            int absBrokenLeft, int absBrokenTop) {
        Control header;
        
        if (isHeader){
            header = container.getSource().getHeader();
        } else {
            header = container.getSource().getFooter();
        }
        
        if (header == null){
            return;
        }
        
        RControl control = (RControl) Manager.getImpl(header);
        int hLeft = control.target.getX() - control.getTarget().getRInsets().getLeft();
        int hTop = control.target.getY() - control.getTarget().getRInsets().getTop();
        
        RFrame parent = control.getParentFrame();
        RFrame root = parent;
        
        while (parent != null){
            RInsets insets = parent.getRInsets();
            
            hLeft += parent.getX() - insets.getLeft();
            hTop += parent.getY() - insets.getTop();
            
            root = parent;
            parent = parent.getROwner().parentFrame;
        }
        
        if (root == null
                || root.getROwner().getSource() != document){
            // the header/footer is not in the current document
            return;
        }
        
        int srcLeft = container.target.getX() - container.target.getRInsets().getLeft();
        int srcTop = container.target.getY() - container.target.getRInsets().getTop();
        
        hLeft = hLeft - absSourceLeft + srcLeft;
        hTop = hTop - absSourceTop + srcTop;

        if (isHeader) {
            if (vertical){
                hTop += absBrokenTop - absSourceTop;
            } else {
                hLeft += absBrokenLeft - absSourceLeft;
            }
        } else {
            if (vertical){
                hTop += container.target.getHeight() + container.target.getRInsets().getVertical();
            } else {
                hLeft += container.target.getWidth() + container.target.getRInsets().getHorizontal();
            }
        }
        
        control.cloneTarget();
        RChild target = control.target;

        target.setX(hLeft);
        target.setY(hTop);
        
        if (container.parentFrame != null){
            int myAbsPos;
            
            if (vertical) {
                myAbsPos = hTop + absSourceTop - srcTop;
            } else {
                myAbsPos = hLeft + absSourceLeft - srcLeft;
            }
            
            List<?> elements = container.getParentFrame().getElements();
            int index = elements.indexOf(container);
            
        } else {
            // TODO document header/footer
        }
    }*/

    protected void validatePages() {
        for (Iterator<?> itPage = print.getPages().iterator(); itPage.hasNext();){
            JRPrintPage page = (JRPrintPage) itPage.next();
            
            for (Iterator<?> itElement = page.getElements().iterator(); itElement.hasNext();){
                if (!validateElement((RChild)itElement.next())){
                    itElement.remove();
                }
            }
            
            if (page.getElements().isEmpty()){
                itPage.remove();
            } 
            else if (singlePage){
                // TODO diminuir o size do documento se houver espaço sobrando
            }
        }
    }

    protected boolean validateElement(RChild item) {
        if (!item.isVisible()){
            return false;
        }
        if (item instanceof RFrame){
            for (Iterator<?> itElement = ((RFrame) item).getElements().iterator(); itElement.hasNext();){
                if (!validateElement((RChild)itElement.next())){
                    itElement.remove();
                }
            }
        }
        return true;
    }

    protected void dispose() {
        // TODO Auto-generated method stub
        
    }
}
